/*
 * $Id: scm.c,v 1.7 2006-07-14 11:17:28 bacon Exp $
 */

#include <xp/msw/scm.h>
#include <xp/bas/memory.h>
#include <windows.h>


static int __control_service (
	xp_scm_t* scm, const xp_char_t* name, int code, xp_svcstatus_t* svcstatus);

static DWORD __trans_svc_type (int type);
static DWORD __trans_start_type (int type);
static int __trans_error_code (int err);
static void __trans_service_status (
	SERVICE_STATUS* status, xp_svcstatus_t* svcstatus);

#define SCM_HANDLE(scm) ((SC_HANDLE)((scm)->handle))

xp_scm_t* xp_scm_open (xp_scm_t* scm, const xp_char_t* host)
{
	SC_HANDLE handle;

	if (scm == XP_NULL) 
	{
		scm =  (xp_scm_t*) xp_malloc (xp_sizeof(xp_scm_t));
		if (scm == XP_NULL) return XP_NULL;
		scm->__dynamic = xp_true;
	}
	else scm->__dynamic = xp_false;

	handle = OpenSCManager (host, XP_NULL, SC_MANAGER_ALL_ACCESS);
	if (handle == XP_NULL) 
	{
		if (scm->__dynamic) xp_free (scm);
		else scm->errnum = __trans_error_code (GetLastError());
		return XP_NULL;
	}

	scm->handle = (void*)handle;
	return scm;
}

int xp_scm_close (xp_scm_t* scm)
{
	if (CloseServiceHandle((SC_HANDLE)scm->handle) == FALSE) 
	{
		scm->errnum = __trans_error_code (GetLastError());
		return -1;
	}

	if (scm->__dynamic) xp_free (scm);
	return 0;
}

int xp_scm_geterrnum (xp_scm_t* scm)
{
	return scm->errnum;	
}

const xp_char_t* xp_scm_geterrstr (xp_scm_t* scm)
{
	static const xp_char_t* __errstr[] = 
	{
		XP_T("no error"),

		XP_T("access denied"),
		XP_T("dependent service running"),
		XP_T("invalid handle"),
		XP_T("invalid parameter"),
		XP_T("invalid service control"),
		XP_T("invalid service name"),
		XP_T("service cannot accept control code"),
		XP_T("service not active"),
		XP_T("request timed out"),
		XP_T("system is shutting down"),
		XP_T("non-existent service"),
		XP_T("service that has been marked for deletion"),
		XP_T("no such database found"),
		XP_T("path not found"),
		XP_T("service already running"),

		XP_T("database locked"),
		XP_T("depending service missing or deleted"),
		XP_T("depending service failed to start"),
		XP_T("service disabled"),
		XP_T("serviced failed to start due to logon failure"),
		XP_T("thread could not be created"),

		XP_T("service exists"),
		XP_T("duplicate service name")
	};

	if (scm->errnum >= 0 && scm->errnum < xp_countof(__errstr)) 
		return __errstr[scm->errnum];

	return XP_T("unknown error");
}

int xp_scm_createsvc (xp_scm_t* scm, 
	const xp_char_t* name, const xp_char_t* path, 
	int svc_type, int start_type)
{
	SC_HANDLE sh;

	sh = CreateService (
		SCM_HANDLE(scm), 
		name, name, SERVICE_ALL_ACCESS, 
		__trans_svc_type(svc_type), 
		__trans_start_type(start_type),
		SERVICE_ERROR_NORMAL, path, XP_NULL, 
		XP_NULL, XP_NULL, XP_NULL, XP_NULL);

	if (sh == XP_NULL)
	{
		scm->errnum = __trans_error_code (GetLastError());
		return -1;
	}

	return 0;
}

int xp_scm_deletesvc (xp_scm_t* scm, const xp_char_t* name)
{
	SC_HANDLE sh;
	int ret;
	
	sh = OpenService (SCM_HANDLE(scm), name, SERVICE_ALL_ACCESS);
	if (sh == XP_NULL) 
	{
		scm->errnum = __trans_error_code (GetLastError());
		return -1;
	}

	ret = (DeleteService(sh) == TRUE)? 0: -1;
	if (ret == -1) scm->errnum = __trans_error_code (GetLastError());

	CloseServiceHandle (sh);
	return ret;
}

int xp_scm_startsvc (xp_scm_t* scm, const xp_char_t* name)
{
	SC_HANDLE sh;
	int ret;

	sh = OpenService (SCM_HANDLE(scm), name, SERVICE_ALL_ACCESS);
	if (sh == XP_NULL) 
	{
		scm->errnum = __trans_error_code (GetLastError());
		return -1;
	}

	ret = (StartService(sh, 0, 0) == TRUE)? 0: -1;
	if (ret == -1) scm->errnum = __trans_error_code (GetLastError());
	
	CloseServiceHandle (sh);	

	return ret;
}

int xp_scm_stopsvc (xp_scm_t* scm, const xp_char_t* name)
{
	return __control_service (scm, name, SERVICE_CONTROL_STOP, XP_NULL);
}

int xp_scm_pausesvc (xp_scm_t* scm, const xp_char_t* name)
{
	return __control_service (scm, name, SERVICE_CONTROL_PAUSE, XP_NULL);
}

int xp_scm_resumesvc (xp_scm_t* scm, const xp_char_t* name)
{
	return __control_service (scm, name, SERVICE_CONTROL_CONTINUE, XP_NULL);
}

int xp_scm_getsvcstatus (xp_scm_t* scm, const xp_char_t* name, xp_svcstatus_t* svcstatus)
{
	return __control_service (scm, name, SERVICE_INTERROGATE, svcstatus);
}

static int __control_service (
	xp_scm_t* scm, const xp_char_t* name, int code, xp_svcstatus_t* svcstatus)
{
	SC_HANDLE sh;
	SERVICE_STATUS status;
	DWORD err;
	int ret;

	sh = OpenService(SCM_HANDLE(scm), name, SERVICE_ALL_ACCESS);
	if (sh == XP_NULL) {
		scm->errnum = __trans_error_code (GetLastError());
		return -1;
	}

	ret = (ControlService(sh, code, &status) == TRUE)? 0: -1;

	if (ret == 0) 
	{
		if (svcstatus != XP_NULL) 
		{
			__trans_service_status (&status, svcstatus);
		}
	}
	else 
	{
		err = GetLastError ();
		if (ret == -1) scm->errnum = __trans_error_code (err);
		if (svcstatus != XP_NULL) 
		{
			if (err == NO_ERROR || 
			    err == ERROR_INVALID_SERVICE_CONTROL ||
			    err == ERROR_SERVICE_CANNOT_ACCEPT_CTRL || 
			    err == ERROR_SERVICE_NOT_ACTIVE) 
			{
				__trans_service_status (&status, svcstatus);
			}
		}
	}

	CloseServiceHandle (sh);
	return ret;
}

static DWORD __trans_svc_type (int type)
{
	DWORD x = 0;

	if (type & XP_SVC_KERNEL_DRIVER) x |= SERVICE_KERNEL_DRIVER;
	if (type & XP_SVC_FILE_SYSTEM_DRIVER) x |= SERVICE_FILE_SYSTEM_DRIVER;
	if (type & XP_SVC_WIN32_OWN_PROCESS) x |= SERVICE_WIN32_OWN_PROCESS;
	if (type & XP_SVC_WIN32_SHARE_PROCESS) x |= SERVICE_WIN32_SHARE_PROCESS;
	if (type & XP_SVC_INTERACTIVE_PROCESS) x |= SERVICE_INTERACTIVE_PROCESS;

	return x;
}

static DWORD __trans_start_type (int type)
{
	return (type == XP_SVC_AUTO_START)? SERVICE_AUTO_START:
	       (type == XP_SVC_BOOT_START)? SERVICE_BOOT_START:
	       (type == XP_SVC_DEMAND_START)? SERVICE_DEMAND_START:
	       (type == XP_SVC_DISABLED)? SERVICE_DISABLED:
	       (type == XP_SVC_SYSTEM_START)? SERVICE_SYSTEM_START: SERVICE_DEMAND_START;
}

static int __trans_error_code (int err)
{
	if (err == ERROR_ACCESS_DENIED) 
		return XP_SCM_EACCDN;
	else if (err == ERROR_DEPENDENT_SERVICES_RUNNING) 
		return XP_SCM_EDEPRN;
	else if (err == ERROR_INVALID_HANDLE) 
		return XP_SCM_EINHND;
	else if (err == ERROR_INVALID_NAME) 
		return XP_SCM_EINNAM;
	else if (err == ERROR_INVALID_PARAMETER) 
		return XP_SCM_EINPAR;
	else if (err == ERROR_INVALID_SERVICE_CONTROL) 
		return XP_SCM_EINSVC;
	else if (err == ERROR_SERVICE_CANNOT_ACCEPT_CTRL) 
		return XP_SCM_ENOACC;
	else if (err == ERROR_SERVICE_NOT_ACTIVE) 
		return XP_SCM_ENOACT;
	else if (err == ERROR_SERVICE_REQUEST_TIMEOUT) 
		return XP_SCM_ETMOUT;
	else if (err == ERROR_SHUTDOWN_IN_PROGRESS) 
		return XP_SCM_ESHTDN;
	else if (err == ERROR_SERVICE_DOES_NOT_EXIST)
		return XP_SCM_ENOSVC;
	else if (err == ERROR_SERVICE_MARKED_FOR_DELETE)
		return XP_SCM_ESVCMD;
	else if (err == ERROR_DATABASE_DOES_NOT_EXIST)
		return XP_SCM_ENODAB;
	else if (err == ERROR_PATH_NOT_FOUND)
		return XP_SCM_ENOPTH;
	else if (err == ERROR_SERVICE_ALREADY_RUNNING)
		return XP_SCM_ESVCRN;

	else if (err == ERROR_SERVICE_DATABASE_LOCKED)
		return XP_SCM_EDBLCK;
	else if (err == ERROR_SERVICE_DEPENDENCY_DELETED)
		return XP_SCM_EDEPDL;
	else if (err == ERROR_SERVICE_DEPENDENCY_FAIL)
		return XP_SCM_EDEPFL;
	else if (err == ERROR_SERVICE_DISABLED)
		return XP_SCM_EDSABL;
	else if (err == ERROR_SERVICE_LOGON_FAILED)
		return XP_SCM_ELOGON;
	else if (err == ERROR_SERVICE_NO_THREAD)
		return XP_SCM_ENOTHR;

	else if (err == ERROR_SERVICE_EXISTS)
		return XP_SCM_EEXIST;
	else if (err == ERROR_DUPLICATE_SERVICE_NAME)
		return XP_SCM_EDUPNM;

	return XP_SCM_ENOERR;
}

static void __trans_service_status (
	SERVICE_STATUS* status, xp_svcstatus_t* svcstatus)
{
	svcstatus->type = 0;
	svcstatus->state = 0;

	if (status->dwServiceType & SERVICE_FILE_SYSTEM_DRIVER) 
		svcstatus->type |= XP_SVC_FILE_SYSTEM_DRIVER;
	if (status->dwServiceType & SERVICE_KERNEL_DRIVER) 
		svcstatus->type |= XP_SVC_KERNEL_DRIVER;
	if (status->dwServiceType & SERVICE_WIN32_OWN_PROCESS) 
		svcstatus->type |= XP_SVC_WIN32_OWN_PROCESS;
	if (status->dwServiceType & SERVICE_WIN32_SHARE_PROCESS) 
		svcstatus->type |= XP_SVC_WIN32_SHARE_PROCESS;
	if (status->dwServiceType & SERVICE_INTERACTIVE_PROCESS) 
		svcstatus->type |= XP_SVC_INTERACTIVE_PROCESS;

	if (status->dwCurrentState == SERVICE_STOPPED) 
		svcstatus->state = XP_SVC_STOPPED;
	else if (status->dwCurrentState == SERVICE_START_PENDING) 
		svcstatus->state = XP_SVC_START_PENDING;
	else if (status->dwCurrentState == SERVICE_STOP_PENDING) 
		svcstatus->state = XP_SVC_STOP_PENDING;
	else if (status->dwCurrentState == SERVICE_RUNNING) 
		svcstatus->state = XP_SVC_RUNNING;
	else if (status->dwCurrentState == SERVICE_CONTINUE_PENDING) 
		svcstatus->state = XP_SVC_RESUME_PENDING;
	else if (status->dwCurrentState == SERVICE_PAUSE_PENDING) 
		svcstatus->state = XP_SVC_PAUSE_PENDING;
	else if (status->dwCurrentState == SERVICE_PAUSED) 
		svcstatus->state = XP_SVC_PAUSED;
}
